package graphics;
/*
 * Zombinoes: Peter Mosher, Michael Pitts
 * TCSS 491 - Computational Worlds
 * Project 1
 */


import java.nio.IntBuffer;
import java.util.ArrayList;
import java.util.List;

import javax.media.opengl.GL2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.glu.GLU;

import physics.PhysicalCollection;

import com.jogamp.common.nio.Buffers;
import com.jogamp.opengl.util.FPSAnimator;

/**
 * This is a graphics tree, which contains one or more nodes that contain graphics
 * content that can be displayed to a viewer.
 * @author Michael Pitts, Peter Mosher
 * @version Feb 5, 2012
 */
public class Tree implements GLEventListener {
	
	/** The frames per second. */
	private static final int FPS = 30;
	
	/** Basic unit by which to scale the display. */
	private static final double UNIT = 1;
	
	private final PhysicalCollection my_physics;
	
	/** Root node of the graphics tree. */
	private InternalNode my_root;
	
	/** If true, this tree is in select test mode. */
	private boolean my_select_mode;
	
	/** The dimensions of the scene. */
	private double left, right, top, bottom;
	
	/** The coordinates of the last cursor select. */
	private int my_x_select, my_y_select;
	
	/**
	 * Creates a new Graphics tree with the given Physics Collection.
	 */
	public Tree(final PhysicalCollection the_physics) {
		my_physics = the_physics;
		my_root = the_physics.getGraphics();
		my_select_mode = false;
	}
	
	/**
	 * Puts the tree in select mode, which will disable rendering to the screen and
	 * capture any possible collisions.
	 */
	public void setSelectMode(final int the_x, final int the_y) {
		my_select_mode = true;
		my_x_select = the_x;
		my_y_select = the_y;
	}

	@Override public void init(final GLAutoDrawable drawable) {
		final GL2 gl = drawable.getGL().getGL2();
		gl.glClear(GL2.GL_COLOR_BUFFER_BIT);
		final IntBuffer selectBuffer = Buffers.newDirectIntBuffer(64);
		gl.glSelectBuffer(selectBuffer.capacity(), selectBuffer);
		FPSAnimator fps = new FPSAnimator(drawable, FPS);
		fps.start();
	}

	@Override public void dispose(final GLAutoDrawable drawable) {
		
	}

	@Override public void display(final GLAutoDrawable drawable) {
		final GL2 gl = drawable.getGL().getGL2();
		gl.glClear(GL2.GL_COLOR_BUFFER_BIT);
		if (my_select_mode) {
			selectMode(gl);
			my_select_mode = false;
		}
		my_physics.crank((float) 1/FPS);
		my_root.display(gl);
	}

	/**
	 * Renders the geomety and tests for overlap with the point defined by
	 * my_x_select and my_y_select.
	 * @param the_gl is the graphics object.
	 */
	private void selectMode(final GL2 the_gl) {
		final GLU glu = GLU.createGLU(the_gl);
		final int viewport[] = new int[4];
		the_gl.glGetIntegerv(GL2.GL_VIEWPORT, viewport, 0);
		the_gl.glMatrixMode(GL2.GL_PROJECTION);
		the_gl.glPushMatrix();
		the_gl.glLoadIdentity();
		glu.gluPickMatrix(my_x_select, (double)(viewport[3] - my_y_select), 1, 1, viewport, 0);
		the_gl.glOrtho(left, right, bottom, top, -1, 1);
		the_gl.glMatrixMode(GL2.GL_MODELVIEW);
		the_gl.glRenderMode(GL2.GL_SELECT);		
		my_root.testSelect(the_gl); // actually renders the objects
		the_gl.glRenderMode(GL2.GL_RENDER);
		the_gl.glMatrixMode(GL2.GL_PROJECTION);
		the_gl.glPopMatrix();
		the_gl.glMatrixMode(GL2.GL_MODELVIEW);
		
		if (my_root.isSelected()) {
			final Node leaf = getSelectedBranch().get(1);
			leaf.update();
		}
	}

	@Override public void reshape(final GLAutoDrawable drawable, final int x, 
			final int y, final int width,final int height) {		
		if (width < height) {
			right = UNIT;
			top = (double)height / width * UNIT;
		} else {
			top = UNIT;
			right = (double)width / height * UNIT;
		}
		bottom = -top;
		left = -right;
		
		GL2 gl = drawable.getGL().getGL2();
		gl.glMatrixMode(GL2.GL_PROJECTION);
		gl.glLoadIdentity();
		gl.glOrtho(left, right, bottom, top, -1, 1);
		gl.glMatrixMode(GL2.GL_MODELVIEW);
	}
	
	/**
	 * Gets the root of this graphis tree.
	 */
	public InternalNode getRoot() {
		return my_root;
	}
	
	/**
	 * Gets the leaf node that was selected from this tree.
	 * @return the leaf Node that was selected, or null if no nodes were selected.
	 */
	public Node getSelectedLeaf() {
		return getSelectedRoot(null);
	}
	
	/**
	 * Gets a list of each node that was selected, and it's child node.  The 
	 * list begins with the root and extends down the tree to the selected root.
	 * @return a List of Nodes, if size is zero no nodes were selected.
	 */
	public List<Node> getSelectedBranch() {
		final List<Node> list = new ArrayList<Node>();
		getSelectedRoot(list);
		return list;
	}
	
	/**
	 * Gets the root node that was selected, and if passed a list will populate
	 * the list with internal selected nodes as well.
	 * @param the_nodes is the list of nodes to be populated, may be null.
	 * @return the root node that was selected, or null if nothing was selected.
	 */
	private Node getSelectedRoot(final List<Node> the_nodes) {
		Node node = null;
		if (my_root.isSelected()) {
			if (the_nodes != null) the_nodes.add(my_root);
			node = getSelectHelper(my_root, the_nodes);
		}
		return node;
	}

	/**
	 * Gets the root node that was selected, and if passed a list will populate
	 * the list with internal selected nodes as well.
	 * @param the_node is the root node to consider.
	 * @param the_nodes is the list of nodes to be populated, may be null.
	 * @return the root node that was selected, or null if nothing was selected.
	 */
	private Node getSelectHelper(final  Node the_node, final List<Node> the_nodes) {
		final List<Node> children = the_node.getChildren();
		Node node = the_node;
		if (children != null) {
			int i = children.size() - 1;
			while (i > -1 && node == the_node) {
				if (children.get(i).isSelected()) {
					if (the_nodes != null) the_nodes.add(children.get(i));
					node = getSelectHelper(children.get(i), the_nodes);
				}
				i--;
			}
		}
		return node;
	}
}
